class Terrain
{
 
 private float[] noiseFactors = new float[] {.05, .005, .005, .1, .005};
 private float[] scaleFactors = new float[] {30, 60, 160, 30, 300};
 private int[] noiseHarmonics = new int[] {5,10,5,3,5};
 private float[] noiseDropOff = new float[] {.3,.6,.6,.2,.5};
 private int[] rMin = new int[] {45, 177, 0, 0, 23};
 private int[] rMax = new int[] {250, 179, 255, 255, 192};
 private int[] gMin = new int[] {150, 101, 0, 0, 56};
 private int[] gMax = new int[] {170, 181, 255, 0, 230};
 private int[] bMin = new int[] {60, 101, 0, 0, 58};
 private int[] bMax = new int[] {70, 136, 255, 0, 235};
 private float xPerlinOffset = 10000;
 private float zPerlinOffset = 10000;
 
 public int level = 0;
 
 public float maxHeight;
 public float minHeight;
 
// private float[] sizeBounds = new float[] {40, 80, 160, 320}; // (upperBound-lowerBound) mod upperStep = 0 (or artifacts appear)
// private float[] sizeSteps = new float[] {2, 4, 8, 16};
 
  private float[] sizeBounds = new float[] {16, 32, 64, 256}; // (upperBound-lowerBound) mod upperStep = 0 (or artifacts appear)
 private float[] sizeSteps = new float[] {2, 4, 8, 16};

//private float[] sizeBounds = new float[] {20, 40}; // (upperBound-lowerBound) mod upperStep = 0 (or artifacts appear)
//private float[] sizeSteps = new float[] {5,10};
 
 
  PImage skybox_top = loadImage("skybox_top.png");
 PImage skybox_north = loadImage("skybox_north.png");
 PImage skybox_south = loadImage("skybox_south.png");
 PImage skybox_east = loadImage("skybox_east.png");
 PImage skybox_west = loadImage("skybox_west.png");
 
 public Terrain()
 {
   setLevel(0);
 }
 
 public void setLevel(int _level)
 {
  level = _level;
  if(level>=noiseFactors.length)
  {
   level=0; 
  }
  maxHeight = scaleFactors[level]/2; 
  minHeight = maxHeight -1;
  noiseDetail(noiseHarmonics[level], noiseDropOff[level]);
 }
 
 public PVector update(float x, float z)
 {
  return new PVector(x, findHeight(x, z)+4, z);
 }
 
 
 public void draw(){
   beginShape(TRIANGLES);
//   noStroke();
//   fill(150, 195, 125);
   int xOriginOriginal = (int) gameCamera.getLocation().x;
   int zOriginOriginal = (int) gameCamera.getLocation().z;
   
   int largestStepSize = (int) sizeSteps[sizeSteps.length-1];   
   int xMod = xOriginOriginal % largestStepSize;
   int zMod = zOriginOriginal % largestStepSize;
   int xOrigin = xOriginOriginal-xMod;
   int zOrigin = zOriginOriginal-zMod;
   for(int i=0; i<sizeBounds.length; i++)
   {
     if(i==0)
     {
      fillSquare(xOrigin,zOrigin,sizeBounds[0],sizeSteps[0]);
     }
     else
     {
       fillSquareMissingPiece(xOrigin, zOrigin, sizeBounds[i], sizeSteps[i], sizeBounds[i-1]);
     }
   }
   float longestSide = sizeBounds[sizeBounds.length-1];
   endShape(TRIANGLES);
   
   //sky   
   noLights();
   tint(64,64,64);
   longestSide = longestSide;
   float skyboxHeight = longestSide+scaleFactors[level];
   beginShape(QUADS); //top
   textureMode(NORMALIZED);
   texture(skybox_top);
   vertex(xOriginOriginal-longestSide, skyboxHeight-10, zOriginOriginal+longestSide, 0 ,0);
   vertex(xOriginOriginal+longestSide, skyboxHeight-10, zOriginOriginal+longestSide, 1, 0);
   vertex(xOriginOriginal+longestSide, skyboxHeight-10, zOriginOriginal-longestSide, 1, 1);
   vertex(xOriginOriginal-longestSide, skyboxHeight-10, zOriginOriginal-longestSide, 0, 1);   
   endShape();
   
   beginShape(QUADS); //north 
   textureMode(NORMALIZED);
   texture(skybox_north);
   vertex(xOriginOriginal-longestSide, skyboxHeight, zOriginOriginal+longestSide, 0 ,1);
   vertex(xOriginOriginal+longestSide, skyboxHeight, zOriginOriginal+longestSide, 1, 1);
   vertex(xOriginOriginal+longestSide, 0, zOriginOriginal+longestSide, 1, 0);
   vertex(xOriginOriginal-longestSide, 0, zOriginOriginal+longestSide, 0, 0);   
   endShape();
   
   beginShape(QUADS); //south 
   textureMode(NORMALIZED);
   texture(skybox_south);
   vertex(xOriginOriginal-longestSide, skyboxHeight, zOriginOriginal-longestSide, 0 ,0);
   vertex(xOriginOriginal+longestSide, skyboxHeight, zOriginOriginal-longestSide, 1, 0);
   vertex(xOriginOriginal+longestSide, 0, zOriginOriginal-longestSide, 1, 1);
   vertex(xOriginOriginal-longestSide, 0, zOriginOriginal-longestSide, 0, 1);
   endShape();
   
   beginShape(QUADS); //east 
   textureMode(NORMALIZED);
   texture(skybox_east);
   vertex(xOriginOriginal+longestSide, skyboxHeight, zOriginOriginal+longestSide, 0 ,0);
   vertex(xOriginOriginal+longestSide, skyboxHeight, zOriginOriginal-longestSide, 0, 1);
   vertex(xOriginOriginal+longestSide, 0, zOriginOriginal-longestSide, 1, 1);
   vertex(xOriginOriginal+longestSide, 0, zOriginOriginal+longestSide, 1, 0);   
   endShape();
   
   beginShape(QUADS); //west 
   textureMode(NORMALIZED);
   texture(skybox_west);
   vertex(xOriginOriginal-longestSide, skyboxHeight, zOriginOriginal+longestSide, 1 ,0);
   vertex(xOriginOriginal-longestSide, skyboxHeight, zOriginOriginal-longestSide, 1, 1);
   vertex(xOriginOriginal-longestSide, 0, zOriginOriginal-longestSide, 0, 1);
   vertex(xOriginOriginal-longestSide, 0, zOriginOriginal+longestSide, 0, 0);  
   endShape();
   /*
   noStroke();
   fill(70, 70, 125); //sky  
   pushMatrix();
   translate(xOrigin, 0, zOrigin);
   rotateX(PI/2);
   scale(500);    
   sky.draw();   
   popMatrix();
   */
   
 }
 
 private void placeQuad(float x, float z, float size)
 {
   vertex(x, findHeight(x, z), z);
   vertex(x-size, findHeight(x-size, z), z);
   vertex(x-size, findHeight(x-size, z+size), z+size);
   vertex(x, findHeight(x, z+size), z+size);
 }
 
  private void placeQuad(float x, float z, float size, float y)
 {
   vertex(x, y, z);
   vertex(x-size, y, z);
   vertex(x-size, y, z+size);
   vertex(x, y, z+size);
 }
 
 public float findHeight(float x, float z)
 {
   return scaleFactors[level] * noise(xPerlinOffset+noiseFactors[level]*x, zPerlinOffset+noiseFactors[level]*z);
 }
 
 public color getColorAtHeight(float y)
 {
  if(y>maxHeight)
  {
   maxHeight=y; 
  } else if(y<minHeight)
  {
   minHeight=y; 
  }
  
  float colorScale = (y-minHeight)/(maxHeight-minHeight);
  
//  colorScale=2*colorScale-.5;

  if(colorScale<0){
   colorScale=0; 
  } else if(colorScale>1)
  {
   colorScale=1;
  }
  
  
//  if(random(10000)<2){
//    println(colorScale);
//    println(gMin[level]+(gMax[level]-gMin[level])*colorScale);
//  }
  return color(rMin[level]+(rMax[level]-rMin[level])*colorScale,gMin[level]+(gMax[level]-gMin[level])*colorScale, bMin[level]+(bMax[level]-bMin[level])*colorScale);
 }
 
 private void fillSquare(float xCenter, float zCenter, float size, float step)
 {
   fillSquareMissingPiece(xCenter, zCenter, size, step, 0);
 }
 
 private void makeSquare(PVector a, PVector b, PVector c, PVector d)
 {
   PVector plane1 = PVector.sub(b,a);
   PVector plane2 = PVector.sub(c,a);
   PVector norm1 = plane2.cross(plane1);
   norm1.normalize();
   
   normal(norm1.x, norm1.y, norm1.z);
   fill(getColorAtHeight(a.y));
   vertex(a.x, a.y, a.z);
   fill(getColorAtHeight(b.y));
   vertex(b.x, b.y, b.z);
   fill(getColorAtHeight(c.y));
   vertex(c.x, c.y, c.z);
   
   PVector plane3 = PVector.sub(d,a);
   PVector norm2 = plane3.cross(plane2);
   norm2.normalize();
  
   normal(norm2.x, norm2.y, norm2.z);
   fill(getColorAtHeight(a.y));
   vertex(a.x, a.y, a.z);
   fill(getColorAtHeight(c.y));
   vertex(c.x, c.y, c.z);
   fill(getColorAtHeight(d.y));
   vertex(d.x, d.y, d.z);
 }
 
 private void makeSmoothSquare(PVector a, PVector b, PVector c, PVector d)
 {

   float interpDistance = PVector.sub(a,b).mag()/2;
   PVector normA = getNorm(a, interpDistance);
   PVector normB = getNorm(b, interpDistance);
   PVector normC = getNorm(c, interpDistance);
   PVector normD = getNorm(d, interpDistance);
   
   normal(normA.x, normA.y, normA.z);
   fill(getColorAtHeight(a.y));
   vertex(a.x, a.y, a.z);
   normal(normB.x, normB.y, normB.z);
   fill(getColorAtHeight(b.y));
   vertex(b.x, b.y, b.z);
   normal(normC.x, normC.y, normC.z);
   fill(getColorAtHeight(c.y));
   vertex(c.x, c.y, c.z);
  
   normal(normA.x, normA.y, normA.z);
   fill(getColorAtHeight(a.y));
   vertex(a.x, a.y, a.z);
   normal(normC.x, normC.y, normC.z);
   fill(getColorAtHeight(c.y));
   vertex(c.x, c.y, c.z);
   normal(normD.x, normD.y, normD.z);
   fill(getColorAtHeight(d.y));
   vertex(d.x, d.y, d.z);
 }
 
 private PVector getNorm(PVector a, float interpDistance)
 {
   PVector normPoint1 = new PVector(a.x+interpDistance, a.y, a.z);
   PVector normPoint2 = new PVector(a.x, a.y, a.z+interpDistance);
   PVector normPoint3 = new PVector(a.x-.7*interpDistance, a.y, a.z-.7*interpDistance);
   normPoint1.y = findHeight(normPoint1.x, normPoint1.z);
   normPoint2.y = findHeight(normPoint2.x, normPoint2.z);
   normPoint3.y = findHeight(normPoint3.x, normPoint3.z);
   PVector plane1 = PVector.sub(normPoint1, normPoint3);
   PVector plane2 = PVector.sub(normPoint2, normPoint3);
   
   PVector normA = plane2.cross(plane1);
   normA.normalize();
   
   return normA;
 }
 
 private void fillSquareMissingPiece(float xCenter, float zCenter, float size, float step, float sizeMissing)
 {
   for(float x = xCenter-size; x<xCenter+size; x+=step)
   {
     for(float z = zCenter-size; z<zCenter+size; z+=step)
     {
       if(!(x-xCenter>=-sizeMissing && x-xCenter<sizeMissing && z-zCenter>=-sizeMissing && z-zCenter<sizeMissing))
       { 
         PVector a = new PVector(x, findHeight(x,z+step), z+step);
         PVector b = new PVector(x, findHeight(x,z), z);
         PVector c = new PVector(x+step, findHeight(x+step,z), z);
         PVector d = new PVector(x+step, findHeight(x+step,z+step), z+step); 
         
         makeSmoothSquare(a,b,c,d);
       }
       
       if(x-xCenter==-sizeMissing && z-zCenter>=-sizeMissing && z-zCenter<sizeMissing) //we are on the right/left border advancing towards positive z
       {
         PVector a1 = new PVector(x,(findHeight(x,z)+findHeight(x,z+step))/2, z+step/2);
         PVector b1 = new PVector(x, findHeight(x,z), z);
         PVector c1 = new PVector(x+step/2, findHeight(x+step/2,z), z);
         PVector d1 = new PVector(x+step/2, findHeight(x+step/2,z+step/2), z+step/2); 
         makeSmoothSquare(a1,b1,c1,d1);
         
         PVector a2 = new PVector(x,findHeight(x,z+step), z+step);
         PVector b2 = new PVector(x, (findHeight(x,z)+findHeight(x,z+step))/2, z+step/2);
         PVector c2 = new PVector(x+step/2, findHeight(x+step/2,z+step/2), z+step/2);
         PVector d2 = new PVector(x+step/2, findHeight(x+step/2,z+step), z+step);         
         makeSmoothSquare(a2,b2,c2,d2);      
       } 
       
       if(x-xCenter==sizeMissing && z-zCenter>=-sizeMissing && z-zCenter<sizeMissing) //we are on the right/left border advancing towards positive z
       {
         
         PVector a1 = new PVector(x-step/2,findHeight(x-step/2,z+step/2), z+step/2);
         PVector b1 = new PVector(x-step/2, findHeight(x-step/2,z), z);
         PVector c1 = new PVector(x, findHeight(x,z), z);
         PVector d1 = new PVector(x, (findHeight(x,z)+findHeight(x,z+step))/2, z+step/2);
         makeSmoothSquare(a1,b1,c1,d1);
         
         PVector a2 = new PVector(x-step/2,findHeight(x-step/2,z+step), z+step);
         PVector b2 = new PVector(x-step/2, findHeight(x-step/2,z+step/2), z+step/2);
         PVector c2 = new PVector(x, (findHeight(x,z)+findHeight(x,z+step))/2, z+step/2);
         PVector d2 = new PVector(x, findHeight(x,z+step), z+step);        
         makeSmoothSquare(a2,b2,c2,d2);            
       }
       
       if(z-zCenter==-sizeMissing && x-xCenter>=-sizeMissing && x-xCenter<sizeMissing) //we are on the right/left border advancing towards positive z
       {
         PVector a1 = new PVector(x,findHeight(x,z+step/2), z+step/2);
         PVector b1 = new PVector(x, findHeight(x,z), z);
         PVector c1 = new PVector(x+step/2, (findHeight(x,z)+findHeight(x+step,z))/2, z);
         PVector d1 = new PVector(x+step/2, findHeight(x+step/2, z+step/2), z+step/2); 
         makeSmoothSquare(a1,b1,c1,d1);
         
         PVector a2 = new PVector(x+step/2,findHeight(x+step/2,z+step/2), z+step/2);
         PVector b2 = new PVector(x+step/2, (findHeight(x,z)+findHeight(x+step,z))/2, z);
         PVector c2 = new PVector(x+step, findHeight(x+step, z), z);
         PVector d2 = new PVector(x+step, findHeight(x+step, z+step/2), z+step/2);        
         makeSmoothSquare(a2,b2,c2,d2);     
       }
       
       if(z-zCenter==sizeMissing && x-xCenter>=-sizeMissing && x-xCenter<sizeMissing) //we are on the right/left border advancing towards positive z
       {
         PVector a1 = new PVector(x,findHeight(x,z), z);
         PVector b1 = new PVector(x, findHeight(x,z-step/2), z-step/2);
         PVector c1 = new PVector(x+step/2, findHeight(x+step/2, z-step/2), z-step/2);
         PVector d1 = new PVector(x+step/2, (findHeight(x,z)+findHeight(x+step,z))/2, z); 
         makeSmoothSquare(a1,b1,c1,d1);
         
         PVector a2 = new PVector(x+step/2,(findHeight(x,z)+findHeight(x+step,z))/2, z);
         PVector b2 = new PVector(x+step/2, findHeight(x+step/2,z-step/2), z-step/2);
         PVector c2 = new PVector(x+step, findHeight(x+step, z-step/2), z-step/2);
         PVector d2 = new PVector(x+step, findHeight(x+step, z), z);        
         makeSmoothSquare(a2,b2,c2,d2);
                    
       }
            
       
     }
   }
 }
}
